package com.cloudcentral.config;

import com.cloudcentral.filter.CaptchaVerifyFilter;
import com.cloudcentral.service.UserService;
import com.cloudcentral.util.Result;
import com.cloudcentral.util.WebUtils;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * Create by Administrator
 * 2023/11/9 15:42
 */
@Configuration
public class SecurityConfig {
  private final UserService userDetailService;
  private final RedissonClient redissonClient;

  @Autowired
  public SecurityConfig(UserService userDetailService, RedissonClient redissonClient) {
    this.userDetailService = userDetailService;
    this.redissonClient = redissonClient;
  }

  @Bean
  @Order(1)
  public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    return http.anonymous().disable()
        .authorizeHttpRequests()
        .requestMatchers(
            AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/**/*.*"),
            AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/**/api-docs/**")).permitAll()
        .requestMatchers("/error", "/login", "/smslogin", "/captcha", "/authorization/**").permitAll()
        .anyRequest().access(new CustomAuthorizationManager())
        .and()
        .csrf().disable()
        .httpBasic()
        .and()
        .addFilterBefore(new CaptchaVerifyFilter(redissonClient), UsernamePasswordAuthenticationFilter.class)

        .formLogin()
        .loginPage("/login.html")
        .loginProcessingUrl("/login")
        .usernameParameter("username")
        .passwordParameter("password")
        //.successHandler((request, response, authentication) -> WebUtils.writeJson(response, Result.success("登录成功")))
        //.failureHandler((request, response, exception) -> WebUtils.writeJson(response, Result.failure(exception.getMessage())))
        .failureHandler((request, response, exception) ->
            response.sendRedirect("/login.html?error=" + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8)))
        .and()

        .apply(new SMSLoginConfigurer<>("/smslogin", userDetailService, redissonClient))
        .loginPage("/login.html")
        .phoneParameter("phone")
        .codeParameter("code")
        //.setSuccessHandler((request, response, authentication) -> WebUtils.writeJson(response, Result.success("登录成功")))
        //.setFailureHandler((request, response, exception) -> WebUtils.writeJson(response, Result.failure(exception.getMessage())))
        .setFailureHandler((request, response, exception) ->
            response.sendRedirect("/login.html?error=" + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8)))
        .and()

        .logout()
        .logoutUrl("/logout")
        .clearAuthentication(true)
        .invalidateHttpSession(true)
        .deleteCookies("SESSION", "JSESSIONID", "JSESSIONID-2", "JSESSIONID-CLIENT", "REMEMBER")
        .logoutSuccessHandler((request, response, authentication) -> {
          String origin = request.getParameter("redirect_uri");
          response.sendRedirect(origin);
        })
        .and()

        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
        .sessionFixation().changeSessionId()
        .maximumSessions(1).maxSessionsPreventsLogin(false)
        .expiredSessionStrategy(event -> WebUtils.writeJson(event.getResponse(), Result.invalid("已在其他地方登录")))
        .and()
        .and()

        .rememberMe()
        .rememberMeParameter("remember")
        .rememberMeCookieName("REMEMBER")
        .tokenValiditySeconds(60 * 60 * 24 * 7) //7天
        .and()

        .cors().disable()

        .headers(customizer -> customizer
            .cacheControl().disable().defaultsDisabled()
            .addHeaderWriter(new StaticHeadersWriter("X-Custom-Security-Header", "header-value"))
            .httpStrictTransportSecurity().includeSubDomains(true).maxAgeInSeconds(60 * 60 * 24).preload(true)
            .and()
            .xssProtection().headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK)
            .and()
            .addObjectPostProcessor(new ObjectPostProcessor<HeaderWriterFilter>() {
              @Override
              public <O extends HeaderWriterFilter> O postProcess(O object) {
                object.setShouldWriteHeadersEagerly(false);
                return object;
              }
            }))

        .exceptionHandling()
        .authenticationEntryPoint((request, response, exception) -> WebUtils.writeJson(response, Result.invalid()))
        .accessDeniedHandler((request, response, exception) -> WebUtils.writeJson(response, Result.denied()))
        .and()

        .build();
  }

  @Bean
  public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
  }

  /**
   * 认证事件发布器
   */
  @Bean
  public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
    return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
  }

}
